package cn.yanghx.pay.feign;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.yanghx.pay.conf.property.PayProperties;
import cn.yanghx.pay.conf.property.WechatAppPayProperties;
import cn.yanghx.pay.exception.WechatAppPayException;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import feign.Client;
import feign.FeignException;
import feign.Request;
import feign.Response;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import feign.httpclient.ApacheHttpClient;
import feign.optionals.OptionalDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Objects;

/**
 * 微信app支付feign配置
 *
 * @author yanghx
 */
public class WechatAppPayFeignConfig {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;
    @Resource
    private PayProperties payProperties;

    @Bean
    public Decoder feignDecoder() {
        return new ResultStatusDecoder(new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))));
    }

    @Bean
    public CloseableHttpClient httpClient() {
        WechatAppPayProperties wechatAppPayProperties = payProperties.getWechat().getApp();

        if (wechatAppPayProperties.isEmpty()) {
            throw new WechatAppPayException("需要配置app支付相关配置");
        }

        String mchId = wechatAppPayProperties.getMchId();
        String mchSerialNo = wechatAppPayProperties.getMchSerialNo();
        String apiV3Key = wechatAppPayProperties.getApiV3Key();
        String privateKey = wechatAppPayProperties.getPrivateKey();
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));
        //不需要传入微信支付证书了
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        //微信提供的签名工具
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
        return builder.build();
    }

    /**
     * 这两个bean的作用是。将feign的请求地址换成httpclient。 然后自己提供httpClient 。 并设置拦截器
     *
     * @param httpClient client
     * @return client
     */
    @Bean
    public Client feignClient(HttpClient httpClient) {
        return new ApacheHttpClient(httpClient);
    }
}


@Slf4j
@Configuration
class StashErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        String body = null;
        try {
            Response.Body body1 = response.body();
            body = IoUtil.read(body1.asReader());
        } catch (Exception ignored) {
        }
        log.error(" wechat-app-pay-feign错误信息。打印body : {}", body);
        Default decoder = new Default();
        return decoder.decode(methodKey, response);
    }
}

/**
 * feign返回值拦截
 *
 * @author liufei
 */
@Slf4j
final class ResultStatusDecoder implements Decoder {
    public static final String CONTENT_KEY = "content";
    final Decoder delegate;

    public ResultStatusDecoder(Decoder delegate) {
        Objects.requireNonNull(delegate, "Decoder must not be null. ");
        this.delegate = delegate;
    }

    @Override
    public Object decode(Response response, Type type) throws IOException {
        Response.Body responseBody = response.body();
        String resultStr = null;
        if (ObjectUtil.isEmpty(responseBody)) {
            log.error("wechat-app-pay-feign 返回值 response body 是空的");
        } else {
            // 判断是否返回参数是否是异常
            resultStr = IOUtils.toString(response.body().asInputStream(), String.valueOf(StandardCharsets.UTF_8));
        }

        // 拿到返回值，进行自定义逻辑处理
        Request request = response.request();
        String url = request.url();
        String body = null;
        try {
            body = new String(request.body());
        } catch (Exception e) {
            ResultStatusDecoder.log.error("读取body失败");
        }
        Request.HttpMethod method = request.httpMethod();
        ResultStatusDecoder.log.info("wechat-app-pay-feign请求拦截器: url {} method {} 请求body {}  result {}", url, method, body, resultStr);
        this.checkResult(resultStr);
        // 回写body,因为response的流数据只能读一次，这里回写后重新生成response
        return delegate.decode(response.toBuilder().body(resultStr, StandardCharsets.UTF_8).build(), type);
    }

    private void checkResult(String resultStr) throws FeignException {
        JSONObject result;
        try {
            result = JSONUtil.parseObj(resultStr);
        } catch (Exception e) {
            ResultStatusDecoder.log.error("错误。返回值不是json格式", e);
            throw new WechatAppPayException("返回值不是json格式");
        }
        Integer code = result.getInt("code");
        if (ObjectUtil.isNotEmpty(code) && 200 != code) {
            ResultStatusDecoder.log.error("wechat-app-pay-feign请求，返回值效验。出现错误 " + resultStr);
            throw new WechatAppPayException(resultStr);
        }
    }
}